import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import '../chore/ALDownloaderFile.dart';
import '../implementation/ALDownloaderFileManagerIMP.dart';
import 'ALDownloaderDirectoryManager.dart';
import 'ALDownloaderFilePropertyDecider.dart';
import 'ALDownloaderPrint.dart';

/// A file manager that generates the default file feature
///
/// When no file path is injected while download in [ALDownloader.download], the file path is generated by [ALDownloaderFileManagerDefault].
abstract class ALDownloaderFileManagerDefault {
  static Future<ALDownloaderFile> lazyGetPhysicalFileForUrl(String url) async {
    // Generate file type model for url.
    final model = ALDownloaderFilePropertyDecider.getFileTypeModelForUrl(url);

    // component directory path
    final componentDirectoryPath =
        model.type.componentDirectoryPathWithUnknownAsPlaceholder;

    // file name
    final fileName = _assembleFileName(url, model);

    final theRootDir = await _theRootDir;

    // directory path
    final directoryPath = theRootDir + componentDirectoryPath;

    await ALDownloaderDirectoryManager.tryToCreateCustomDirectory(directoryPath,
        recursive: true);

    final file = ALDownloaderFile(directoryPath, fileName);

    return file;
  }

  static Future<String> lazyGetPhysicalDirectoryPathForUrl(String url) async {
    final file = await lazyGetPhysicalFileForUrl(url);
    final directoryPath = file.directoryPath;

    return directoryPath;
  }

  static Future<String> getVirtualDirectoryPathForUrl(String url) async {
    // Generate file type model for url.
    final model = ALDownloaderFilePropertyDecider.getFileTypeModelForUrl(url);

    // level 1 folder - component
    final aDirString =
        model.type.componentDirectoryPathWithUnknownAsPlaceholder;
    final theRootDir = await _theRootDir;
    final dirForRootToFirstLevel = theRootDir + aDirString;
    return dirForRootToFirstLevel;
  }

  static Future<String> getVirtualFilePathForUrl(String url) async {
    // Generate file type model for url.
    final model = ALDownloaderFilePropertyDecider.getFileTypeModelForUrl(url);

    // level 1 folder - component
    final aDirString =
        model.type.componentDirectoryPathWithUnknownAsPlaceholder;
    final theRootDir = await _theRootDir;
    final dirForRootToFirstLevel = theRootDir + aDirString;
    final fileName = _assembleFileName(url, model);

    final filePath = dirForRootToFirstLevel + fileName;

    return filePath;
  }

  static Future<String?> getPhysicalFilePathForUrl(String url) async {
    String virtualfilePath =
        await getVirtualFilePathForUrl(url); // virtual file path

    String? filePath;

    try {
      final aFile = File(virtualfilePath); // physical file
      if (aFile.existsSync()) filePath = virtualfilePath;
    } catch (error) {
      aldDebugPrint(
          'ALDownloaderFileManagerDefault | getPhysicalFilePathForUrl, error: $error');
    }

    return filePath;
  }

  /// Check whether physical file path of [url] exists
  static Future<bool> isExistPhysicalFilePathForUrl(String url) async =>
      await getPhysicalFilePathForUrl(url) != null;

  /// Check whether [path] is in [_theRootDir]
  static Future<bool> isInRootPathForPath(String path) async {
    final theRootDir = await _theRootDir;
    return ALDownloaderFileManagerIMP.cIsInRootPathForPath(path, theRootDir);
  }

  static String getFileNameForUrl(String url) {
    final model = ALDownloaderFilePropertyDecider.getFileTypeModelForUrl(url);

    final fileName = _assembleFileName(url, model);
    return fileName;
  }

  /// Get root path
  static Future<String> get _theRootDir async {
    String? aDir;
    if (Platform.isIOS || Platform.isOhos) {
      aDir = await ALDownloaderDirectoryManager.localDocumentDirectory;
    } else if (Platform.isAndroid) {
      aDir = await ALDownloaderDirectoryManager.localExternalStorageDirectory;
      if (aDir == null)
        aDir = await ALDownloaderDirectoryManager.localDocumentDirectory;
    } else {
      throw 'ALDownloaderFileManagerDefault | get _theRootDir, error: ALDownloader can not operate on current platform ${Platform.operatingSystem}';
    }

    return aDir;
  }

  /// Get result that assembles file name for [url] and [model]
  static String _assembleFileName(String url, ALDownloaderFileTypeModel model) {
    final StringBuffer sb = StringBuffer();

    final md5String = _getMd5StringForString(url);
    sb.write(md5String);

    final description = model.description;
    if (description != null && description.length > 0) sb.write(description);
    return sb.toString();
  }

  static String _getMd5StringForString(String aString) {
    final content = Utf8Encoder().convert(aString);
    final digest = md5.convert(content);
    return hex.encode(digest.bytes);
  }

  /// Privatize constructor
  ALDownloaderFileManagerDefault._();
}
